<template>
    <view class="flex flex-col">
        <scroll-view  :refresher-triggered="Loading"  @scrolltolower="pullEnd"  :scroll-with-animation="true"  @scroll="scroll" :scroll-y="true" 
	    :style="[{height:rootHeight+'px','overflow-anchor':'auto',width:`${props.width}rpx`}]">
            <tm-sheet :height="40" unit="px" v-if="Loading&&pullType=='top'"  :margin="[0,0]" _class="flex flex-col flex-col-center-center">
                <tm-icon :color="props.color"  :font-size="24" v-if="status=='loading'" spin name="tmicon-loading"></tm-icon>
                <view @click="reset('pullTop')" v-if="status=='error'" class="flex flex-row flex-center">
                    <tm-icon :userInteractionEnabled="false" color="red" :font-size="24"  name="tmicon-times-circle-fill"></tm-icon>
                    <tm-text :userInteractionEnabled="false" color="red" :font-size="24" _class="pl-16" label="加载失败,点我重试"></tm-text>
                </view>
            </tm-sheet>
            <view class="flex flex-col relative" :style="[{height:totalHeight+'px'}]">
                <view class="absolute l-0 t-0 flex flex-col" :style="{transform:`translateY(${offsetY}px)`,width:`${props.width}rpx`}">
                    <slot name="default" :data="visibleItems"></slot>
                </view>
            </view>
            <tm-sheet :height="40" unit="px" v-if="Loading&&pullType=='bottom'"  :margin="[0,0]" _class="flex flex-col flex-col-center-center">
                <tm-icon :color="props.color" :font-size="24" v-if="status=='loading'" spin name="tmicon-loading"></tm-icon>
                <view @click="reset('pullEnd')" v-if="status=='error'" class="flex flex-row flex-center">
                    <tm-icon :userInteractionEnabled="false" color="red" :font-size="24"  name="tmicon-times-circle-fill"></tm-icon>
                    <tm-text :userInteractionEnabled="false" color="red" :font-size="24" _class="pl-16" label="加载失败,点我重试"></tm-text>
                </view>
            </tm-sheet>
        </scroll-view>
        
    </view>

</template>
<script lang="ts" setup>
/**
 * 虚拟列表
 * @description 用来展示长列表数据使用，采用虚拟数据展示，只展示视窗内数据，其它不展示。因此上万条列表数据，也可以轻松展示。
 * @example 下面演示了加载1900张图片的示例
 * <tm-virtual-list :width="750" :height="800" :data="imglist" :itemHeight="160">
		<template v-slot:default="{data}">
			<tm-image-group >
				<tm-image :padding="[5,5]" preview :width="750" :height="150" :src="item" 
				v-for="(item,index) in data" :key="index"></tm-image>
			</tm-image-group>
		</template>
	</tm-virtual-list>
	-----js生成1900张图片链接。-----
	const imglist = ref([])
	for(let i=0;i<1900;i++){
		imglist.value.push('https://picsum.photos/200/300?id='+i)
	}
 */
import { ref,computed,Ref, PropType, onMounted } from "vue";
import {scrollDetailFace,statusType} from "./interface"
import tmSheet from "../tm-sheet/tm-sheet.vue";
import tmIcon from "../tm-icon/tm-icon.vue";
import tmText from "../tm-text/tm-text.vue";
const emits = defineEmits(["pullEnd","pullStart","status"])
const props = defineProps({
    width: {
        type: Number,
        default: 300,
    },
    height: {
        type: Number,
        default: 500,
    },
    //预估项目的高度，必填。
    itemHeight: {
        type: Number,
        default: 0,
        required: true,
    },
    data: {
        type: Array,
        default: () => [],
    },
    //触底结束或者下拉刷新前执行，可以返回Promise<boolean>,真，结束触底操作，假触底加载中
    load:{
        type:[Function,Boolean],
        default:()=>true
    },
    //首次加载渲染时，是否触发加载数据事件。
    firstLoad:{
        type:Boolean,
        default:true
    },
    color:{
        type:String,
        default:"primary"
    }
});
const rowHeight = uni.upx2px(props.itemHeight)
const rootHeight = uni.upx2px(props.height)
const scrollTop = ref(0)
const renderAhead = 2
const rowCount = computed(()=>props.data.length)
const childPositions = computed(()=>{
    const results = [0];
    for (let i = 1; i < rowCount.value; i++) {
        results.push(results[i - 1] + rowHeight);
    }
    return results;
})
const totalHeight = computed(()=>{
    return rowCount.value
                ? childPositions.value[rowCount.value - 1] + rowHeight
                : 0;
})
const firstVisibleNode = computed(()=>findStartNode())
const startNode = computed(()=>Math.max(0, firstVisibleNode.value - renderAhead))
const lastVisibleNode = computed(()=>findEndNode())
const endNode = computed(()=>Math.min(rowCount.value - 1, lastVisibleNode.value +renderAhead))
const visibleNodeCount = computed(()=>endNode.value - startNode.value + 1)
const offsetY = computed(()=>childPositions.value[startNode.value])
const visibleItems = computed(()=>{
    return props.data.slice(
        startNode.value,
        startNode.value + visibleNodeCount.value,
    );
})

const Loading = ref(false)
const pullType = ref("")
/**
 * never 从未加载过。
 */
const status:Ref<statusType> = ref("never")

function findStartNode() {
    let startRange = 0;
    let endRange = rowCount.value ? rowCount.value - 1 : rowCount.value;

    while (endRange !== startRange) {
        const middle = Math.floor((endRange - startRange) / 2 + startRange);

        if (
            childPositions.value[middle] <= scrollTop.value
            && childPositions.value[middle + 1] > scrollTop.value
        ) {
            return middle;
        }
        if (middle === startRange) {
            // edge case - start and end range are consecutive
            return endRange;
        }
        if (childPositions.value[middle] <= scrollTop.value) {
            startRange = middle;
        } else {
            endRange = middle;
        }
    }
    return rowCount.value;
}
function findEndNode() {
    let endNode;
    for (endNode = firstVisibleNode.value; endNode < rowCount.value; endNode++) {
        if (childPositions.value[endNode] > childPositions.value[ firstVisibleNode.value] + rootHeight) {
            return endNode;
        }
    }
    return endNode;
}

function scroll(e:any){
   let detail:scrollDetailFace = e.detail;
   scrollTop.value = detail.scrollTop
   if(Math.ceil(scrollTop.value)<-80){
        pullStart("pullStart")
   }
   if(Math.ceil(scrollTop.value)>=0&&status.value=="error"){
        Loading.value=false;
   }
}

const pullStart = async (e:any)=>{
    emits("pullStart")
    if (typeof props.load === 'function') {
        if(Loading.value) return;
        pullType.value = "top"
        Loading.value = true;
        status.value = "loading"
        let p = await props.load('top');
        if(typeof p === 'function'){
            p = await p('top');
        }
        if(!p){
            status.value = "error"
            return;
        }
        Loading.value = false;
        status.value = "success"
    }
}


const pullEnd = async (e:any)=>{
    emits("pullEnd")
    if (typeof props.load === 'function') {
        if(Loading.value) return;
        pullType.value = "bottom"
        Loading.value = true;
        status.value = "loading"
        let p = await props.load('bottom');
        if(typeof p === 'function'){
            p = await p('bottom');
        }
        if(!p){
            status.value = "error"
            return;
        }
        Loading.value = false;
        status.value = "success"
    }
}

const reset = function(e:string){
    Loading.value = false;
    if(e=='pullEnd'){
        pullEnd('bottom');
    }else{
        pullStart('top');
    }
}

onMounted(()=>{
    if(props.firstLoad){
        pullStart('top')
    }
})



</script>
